/*
 * @(#)ParameterGroup.java     14/08/02
 * 
 * Author             Rick Wilson
 * Copyright (c) 2014 Rick Wilson
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 *     * Redistributions of source code must retain the above copyright
 *       notice, this list of conditions and the following disclaimer.
 *     * Redistributions in binary form must reproduce the above copyright
 *       notice, this list of conditions and the following disclaimer in the
 *       documentation and/or other materials provided with the distribution.
 *     * Neither the name of 'Taareekah' nor the
 *       names of its contributors may be used to endorse or promote products
 *       derived from this software without specific prior written permission.
 * 
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
 * DISCLAIMED. IN NO EVENT SHALL <COPYRIGHT HOLDER> BE LIABLE FOR ANY
 * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 *
 */



package parameters;

//~--- non-JDK imports --------------------------------------------------------

import org.w3c.dom.Attr;
import org.w3c.dom.Document;
import org.w3c.dom.Element;

//~--- JDK imports ------------------------------------------------------------

import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;

import javax.management.JMException;

/**
 * A {@linkplain parameters.ParameterNode node} which has child ParameterNodes.
 * @author rick
 */
public class ParameterGroup extends ParameterNode {

    /** Field description */
    private Map<String, ParameterNode>	children;

    /**
     *
     * @param key
     * @param type
     */
    public ParameterGroup(String key, String type) {
	this.key	= key;
	this.parent	= null;
	this.type	= type;
	children	= new HashMap<>();
    }

    /**
     *
     * @param key
     * @param parent
     * @param type
     */
    public ParameterGroup(String key, ParameterNode parent, String type) {
	this.key	= key;
	this.parent	= parent;
	this.type	= type;
	children	= new HashMap<>();
    }

    /**
     * Returns this node as an XMLElement. Adds all children nodes to the created element.
     * @param doc The document the element will belong to.
     * @return An element.
     * @throws Exception
     */
    @Override
    public Element createXMLElement(Document doc) throws Exception {
	Element	root	= doc.createElement("parametergroup");
	Attr	attr	= doc.createAttribute("key");

	attr.setValue(key);
	root.setAttributeNode(attr);

	Attr	type_attr	= doc.createAttribute("type");

	type_attr.setValue(type);
	root.setAttributeNode(type_attr);

	for (ParameterNode p : getChildren()) {
	    root.appendChild(p.createXMLElement(doc));
	}

	return root;
    }

    /**
     *
     * @return
     */
    public Set<ParameterNode> getAllUnsetNodes() {
	Set<ParameterNode>	unset	= new HashSet<>();

	for (Map.Entry<String, ParameterNode> entry : children.entrySet()) {
	    if (!entry.getValue().isSet()) {
		if (entry.getValue().isGroup()) {
		    try {
			ParameterGroup	group	= (ParameterGroup) entry.getValue();

			unset.addAll(group.getAllUnsetNodes());
		    } catch (Exception e) {}
		} else {
		    unset.add(entry.getValue());
		}
	    }
	}

	return unset;
    }

    /**
     *
     * @return
     */
    @Override
    public boolean isSet() {
	for (Map.Entry<String, ParameterNode> entry : children.entrySet()) {
	    if (!entry.getValue().isSet()) {
		return false;
	    }
	}

	return true;
    }

    /**
     * Returns a collection of this nodes children.
     * @return Our children.
     */
    @Override
    public Collection<ParameterNode> getChildren() {
	return children.values();
    }

    /**
     * Returns the number of child nodes.
     * @return The number of child nodes.
     */
    @Override
    public int getNumChildren() {
	return children.size();
    }

    /**
     * Checks whether child_key refers to a valid child node. The key can have an arbitrary number of levels.
     * @param child_key A string in the form of "key0.key1. ... .keyN"
     * @return True if the key is valid, false otherwise.
     */
    public boolean hasChild(String child_key) {
	String[]	split	= child_key.split("[.]", 2);

	// This should never occur but just in case
	if (split.length == 0) {
	    return false;
	} else if (split.length == 1) {						// child key was a single name
	    return children.containsKey(child_key);
	} else if ((split.length > 1) && (split[0] == key)) {			// Child key was qualified with this nodes name
	    return hasChild(split[1]);
	} else if ((split.length > 1) && children.containsKey(split[0])) {	// Child key is a child of a child

	    // If the root key is not a group then the key is invalid
	    if (!children.get(split[0]).isGroup()) {
		return false;
	    }

	    try {
		ParameterGroup	child	= (ParameterGroup) children.get(split[0]);

		return child.hasChild(split[1]);
	    } catch (Exception e) {
		return false;
	    }
	}

	return false;
    }

    /**
     * Adds child as a child node. Sets child's parent to this and ensures
     * child is not already a child.
     * @param child The new node.
     */
    public void addChild(ParameterNode child) {
	if (hasChild(child.getKey())) {
	    children.get(child.getKey()).setParent(null);
	}

	child.setParent(this);
	children.put(child.getKey(), child);
    }

    /**
     * Returns the node specified by child_key. child_key can have arbitrary depth.
     * @param child_key A string in the form of "key0.key1. ... .keyN" case sensitive
     * @return The child node if it exists.
     * @throws Exception
     */
    @Override
    public ParameterNode getChild(String child_key) throws Exception {
	String[]	split	= child_key.split("[.]", 2);

	if (split.length == 0) {
	    throw new JMException("Invalid key");
	} else if (split.length == 1) {
	    return children.get(child_key);
	} else if ((split.length > 1) && (split[0] == key)) {
	    return getChild(split[1]);
	} else {
	    ParameterGroup	child	= (ParameterGroup) children.get(split[0]);

	    return child.getChild(split[1]);
	}
    }

    /**
     * Returns whether this node is a group.
     * @return true
     */
    @Override
    public boolean isGroup() {
	return true;
    }

    /**
     * Method description
     *
     *
     * @return
     */
    @Override
    public String toString() {
	String	children_string	= "";
	int	loop_count	= 0;

	for (Map.Entry<String, ParameterNode> entry : children.entrySet()) {
	    String	entry_ret	= String.format("(Child %1d: %2s)", loop_count, entry.getValue().toString());

	    if (loop_count > 0) {
		children_string	+= ", ";
	    }

	    children_string	+= entry_ret;
	    loop_count++;
	}

	String	ret	= String.format("%1s Key: %2s, Children: %3s", "ParameterGroup", key, children_string);

	return ret;
    }
}


//~ Formatted by Jindent --- http://www.jindent.com
